/**
 * 
 */
package inz.common;

import inz.StartInzynier;
import inz.model.classification.ClassificationHelper;
import inz.model.patterns.GSP;
import inz.model.patterns.SequenceDatabase;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

/** Module responsible for management of all application loggers */
public class LoggingModule
{
	// names of loggers, can be accessed globaly
	public static final String GSP_LOGGER = GSP.class.getSimpleName();
	public static final String CLASSIFICATION_LOGGER = ClassificationHelper.class.getSimpleName();
	public static final String GENERAL_LOGGER = StartInzynier.class.getSimpleName();
	public static final String DATABASE_LOGGER = SequenceDatabase.class.getSimpleName();
	public static final String PROGRESS_LOGGER = "progressLogger";

	// progress variables
	private static final int progressChars = 100;
	private static long processedItems, printedChars;
	private static double step;
	private static String category;

	/** Map of hard references to loggers to avoid garbage collecting loggers. In application loggers are accessed
	 * through Logger.getLogger("name") */
	private static final Map<String, Logger> loggers = new HashMap<String, Logger>();

	static
	{
		setUpLoggers();
	}


	/** creates and sets configurations for loggers */
	private static void setUpLoggers()
	{
		// statistics, separator, result, creating, sizes, loading
		addLogger(GSP_LOGGER, new MyFormatter(),
				Arrays.asList("statistics", "separator", "result", "creating", "sizes", "loading"));
		// examples, SpecificExamples, course, evaluation, deepsEval, jeps, borders, testResult
		addLogger(CLASSIFICATION_LOGGER, new MyFormatter(), Arrays.asList("testResult"));
		// statistics, test, urls
		addLogger(GENERAL_LOGGER, new MyFormatter(), Arrays.asList("statistics", "test", "urls"));
		// statistics, separator, creating
		addLogger(DATABASE_LOGGER, new MyFormatter(), Arrays.asList("statistics", "separator", "creating"));

		// classification, files, gspGenerate, gspCalculate
		addLogger(PROGRESS_LOGGER, new MyFormatter(false),
				Arrays.asList("files", "gspGenerate", "gspCalculate", "deepsEval"));

	}

	/** Adds file writer to given logger
	 * @param nameOfLogger
	 * @param fileName name of file where logs will be written */
	private static void setFileHandler(String nameOfLogger, String fileName)
	{
		try
		{

			FileHandler classificationFileHandler = new FileHandler(fileName);
			classificationFileHandler.setFormatter(new MyFormatter());

			Handler[] handlers = loggers.get(nameOfLogger).getHandlers();
			for (Handler handler : handlers)
			{
				if (handler instanceof FileHandler)
				{
					loggers.get(nameOfLogger).removeHandler(handler);
				}
			}

			loggers.get(nameOfLogger).addHandler(classificationFileHandler);

		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	/** adds new logger in application */
	private static void addLogger(String name, Formatter formatter, Collection<String> logTypes)
	{
		// create logger
		Logger logger = Logger.getLogger(name);

		// disable default console handler (of root handler)
		logger.setUseParentHandlers(false);

		// create output handler for logger
		ConsoleHandler consoleHandler = new ConsoleHandler();

		// set formatter for logger handler
		consoleHandler.setFormatter(formatter);

		// set filter for handler (optional if setting filters directly to loggers)
		// consoleHandler.setFilter(new ActivitesFilter(Arrays.asList("a", "b", "c")));

		// set filters for logger (optional if setting filters to handlers)
		logger.setFilter(new ActivitesFilter(logTypes));

		// set handler for logger
		logger.addHandler(consoleHandler);

		// set level of logger (not necessary if using filters)
		logger.setLevel(Level.INFO);

		// add new logger to collection of application loggers to avoid gc
		loggers.put(name, logger);
	}

	/** Adds file handler to given logger
	 * @param outputFile file where logs will be saved */
	public static void setOutputFile(String loggerName, String outputFile)
	{
		setFileHandler(loggerName, outputFile);
	}

	/** My custom formatter */
	private static class MyFormatter extends Formatter
	{
		private boolean addNewLine = true;
		public MyFormatter()
		{
		}
		
		public MyFormatter(boolean addNewline)
		{
			this.addNewLine = addNewline;
		}
		@Override
		public String format(LogRecord record)
		{
			if (addNewLine)
			{
				return formatMessage(record) + "\n";
			}
			else
			{
				return formatMessage(record);
			}
		}
	}

	/** Class used to filter logs that are not among allowed types */
	private static class ActivitesFilter implements Filter
	{
		private Set<String> loggedActivities = new HashSet<String>();

		/** @param activities collection of allowed (logged) types */
		public ActivitesFilter(Collection<String> activities)
		{
			loggedActivities.addAll(activities);
		}

		@Override
		public boolean isLoggable(LogRecord record)
		{
			String param = (String) record.getParameters()[0];
			return loggedActivities.contains(param);
		}
	}

	/** Prints upper line before displaying progress
	 * @param category category (activity, type) of log
	 * @param message message to display above progress line
	 * @param allItems */
	public static void startProgress(String category, String message, long allItems)
	{
		LoggingModule.category = category;
		processedItems = 0;
		printedChars = 0;
		step = (double) allItems / LoggingModule.progressChars;

		int oneSide = (progressChars - message.length()) / 2;
		StringBuffer upperLine = new StringBuffer();
		upperLine.append('|');
		for (int i = 0; i < oneSide; i++)
		{
			upperLine.append('-');
		}
		upperLine.append(message);
		if (upperLine.length() + oneSide < progressChars + 1)
		{
			oneSide++;
		}
		for (int i = 0; i < oneSide; i++)
		{
			upperLine.append('-');
		}
		upperLine.append("|\n");
		loggers.get(PROGRESS_LOGGER).log(Level.INFO, upperLine.toString(), category);
		loggers.get(PROGRESS_LOGGER).log(Level.INFO, " ", category);
	}

	/** Another item has been processed */
	public static void tickProgress()
	{
		processedItems++;
		while (processedItems / step > printedChars && printedChars < LoggingModule.progressChars)
		{
			loggers.get(PROGRESS_LOGGER).log(Level.INFO, "#", category);
			printedChars++;
		}
	}

	/** Ends progress drawing (printing new line) */
	public static void endProgress()
	{
		loggers.get(PROGRESS_LOGGER).log(Level.INFO, "\n", category);
	}

	public static void main(String[] args)
	{
		setUpLoggers();
		// Logger logger = Logger.getLogger("formatter1");
		// Logger logger2 = Logger.getLogger("formatter1");
		// logger.setUseParentHandlers(false);
		// logger2.setUseParentHandlers(false);
		//
		// MyFormatter formatter = new MyFormatter();
		// ConsoleHandler handler = new ConsoleHandler();
		// handler.setFormatter(formatter);
		//
		// logger.addHandler(handler);
		// logger.setLevel(Level.WARNING);
		//
		// logger.info("Example of creating custom formatter.");
		// logger2.info("info message 2");
		// logger.warning("A warning message.");
		// logger2.warning("A warning message 2.");
		// logger.severe("A severe message.");
		// logger2.severe("A severe message 2.");

		Logger gspLogger = Logger.getLogger(GSP_LOGGER);
		Logger classLogger = Logger.getLogger(CLASSIFICATION_LOGGER);
		gspLogger.log(Level.INFO, "gsp log type result", "result");
		classLogger.log(Level.INFO, "class log", "example");
		// gspLogger.log(Level.INFO, "gsp log type c", "c");

	}

}
